package IC;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;

import IC.AST.ASTPrinter;
import IC.AST.CodePrinter;
import IC.AST.nodes.*;
import IC.Analyzer.*;
import IC.Lexer.Lexer;
import IC.Parser.LibraryParser;
import IC.Parser.Parser;
import IC.asm.ASMEntry;
import IC.asm.ASMGenerator;
import IC.asm.printer.AttPrinter;
import IC.asm.printer.IntelPrinter;
import IC.asm.RegAllocation.RegisterAllocator;
import IC.asm.RegAllocation.SimpleAllocator;
import IC.lir.LIREntry;
import IC.lir.LIRGenerator;
import IC.lir.LIROptimizer;
import IC.lir.LIRPrinter;

/**
 * Main Compiler Class
 */
public class Compiler {


	public static String sourceFileName = "";

	@Option(name="-print-ast",usage="print the ast structure")
	private boolean isPrintAST;

	@Option(name="-dump-symtab",usage="print the symbol table structure")
	private boolean isPrintSymTab;

	@Option(name="-dump-ast-symtab",usage="print the symbol table structure with the ast")
	private boolean isPrintASTSymTab;

	@Option(name="-dump-tokens",usage="print the tokens")
	private boolean isPrintTokens;

	@Option(name="-dump-code",usage="print the code")
	private boolean isPrintCode;

	@Option(name="-print-lir-console", usage="print lir code on screen")
	private boolean isPrintLirConsole;

	@Option(name="-print-lir", usage="print lir code to a file")
	private boolean isPrintLir;

	@Option(name="-overwrite", usage="overwrite an existing file")
	private boolean isOverwrite;

	@Option(name="-opt-lir", usage="optimize lir code")
	private boolean isOptimizeLir;
	
	@Option(name="-intel", usage="print asm code")
	private boolean isIntel;

	@Option(name="-L",usage="library file")
	private String libFilePath = "libic.sig";

	@Argument
	private List<String> arguments = new ArrayList<String>();

	public void doMain(String[] args) throws IOException {
		CmdLineParser parser = new CmdLineParser(this);

		parser.setUsageWidth(80);

		try {
			// parse the arguments.
			parser.parseArgument(args);

			if( arguments.isEmpty() )
				throw new CmdLineException("No argument is given");

		} catch( CmdLineException e ) {
			System.out.println(e.getMessage());
			System.out.println("Usage: java IC.Compiler <file.ic> [ -L </path/to/libic.sig> ] [ options ] \n ");
			// print the list of available options
			parser.printUsage(System.out);
			System.out.println();
			System.exit(1);
		}

		sourceFileName = arguments.get(0);

		File f = new File(libFilePath);
		if(!f.exists()) {
			System.err.println("Could not open library file "+libFilePath);
			System.err.println("Continuing without libic ...");
			libFilePath = null;
		}

	}

	public static void main(String[] args) {

		Compiler c = new Compiler();
		try {
			c.doMain(args);
		} catch (IOException e2) {
			e2.printStackTrace();
		}

		/**
		 * read input the file
		 */
		FileReader sourceFile = null;
		FileReader libFile = null;
		try {
			if(c.libFilePath != null) {
				libFile = new FileReader(c.libFilePath);
			}
			sourceFile = new FileReader(c.arguments.get(0));
		} catch (FileNotFoundException e1) {
			System.err.println("Could not open input file "+e1.getMessage());
			System.exit(-1);
		}

		/**
		 * make the AST
		 */
		try {
			//String sep = "";
			Program progAST;

			/** parse the source file **/
			Parser parser = new Parser(new Lexer(sourceFile));
			parser.printTokens = c.isPrintTokens;
			progAST = (Program)parser.parse().value;
			//System.out.println("Parsed file "+ Compiler.sourceFileName +" successfully");
			//System.out.print(sep);

			/** parse the library file **/
			if (libFile != null) {
				LibraryParser libParser = new LibraryParser(new Lexer(libFile));
				//if (libParser.printTokens = c.isPrintTokens) {
				//	sep = "\n ------- \n\n";
				//}
				ClassDecl libAST = (ClassDecl)(libParser.parse().value);
				//System.out.println("Parsed file "+ c.libFilePath +" successfully");
				//System.out.print(sep);

				/** add the library ClassDecl to the overall AST **/
				progAST.addClassDecl(libAST);
			}

			/** print the ast **/
			if (c.isPrintAST) {
				(new ASTPrinter(progAST)).print();
			}
			if(c.isPrintCode) {
				(new CodePrinter(progAST)).print();
			}

			/** build the symbol table **/
			new SymTabBuilder(progAST);
			if(c.isPrintASTSymTab) {
				ASTSymTabPrinter printer = new ASTSymTabPrinter(progAST);
				printer.print();
			}
			if(c.isPrintSymTab) {
				SymTabPrinter printer = new SymTabPrinter(progAST);
				printer.print();
			}
			new TypeChecker(progAST);
			new StructureChecker(progAST);

			if(c.isPrintLirConsole) {
				IC.lir.LoweringVisitor.Process(progAST);
				List<LIREntry> lirCode = LIRGenerator.Process(progAST);
				if(c.isOptimizeLir) {
					lirCode = LIROptimizer.Process(lirCode);
				}
				LIRPrinter.Print(lirCode);
			}

			if(c.isPrintLir) {
				PrintStream orig_is = System.out;
				FileOutputStream fos = null;

				int index = sourceFileName.lastIndexOf(".ic");
				String lirFileName = sourceFileName.substring(0,index).concat(".lir");
				File f = new File(lirFileName);
				if(!f.createNewFile() && !c.isOverwrite) {
					System.err.println("File " + lirFileName + " already exists, \n" +
					"use the -overwrite flag if you want to overwrite this file");
				}
				else if(c.isOverwrite) {
					f.delete();
					f.createNewFile();
				}
				fos = new FileOutputStream(lirFileName);

				PrintStream ps = new PrintStream(fos);
				System.setOut(ps);

				IC.lir.LoweringVisitor.Process(progAST);
				List<LIREntry> lirCode = LIRGenerator.Process(progAST);
				if(c.isOptimizeLir) {
					lirCode = LIROptimizer.Process(lirCode);
				}
				LIRPrinter.Print(lirCode);

				System.setOut(orig_is);
				//System.out.println("Lir code written to: " + lirFileName );
			}

			/** print Assembly code */
			
			IC.asm.LoweringVisitor.Process(progAST);
			List<ASMEntry> asmCode = ASMGenerator.Process(progAST);
			asmCode = SimpleAllocator.Process(asmCode);
			
			// prepare output stream
			PrintStream orig_is = System.out;
			
			// prepare filename
			int index = sourceFileName.lastIndexOf(".ic");
			String asmFileName = sourceFileName.substring(0,index).concat(".s");
			
			// create file
			File f = new File(asmFileName);
			if(!f.createNewFile()) {
				f.delete();
				if(!f.createNewFile()) {
					System.err.println("Could not create file " + asmFileName);
					return;
				}
			}
			
			PrintStream ps = new PrintStream(new FileOutputStream(asmFileName));
			System.setOut(ps);
			
			// print the code itself
			if(c.isIntel) {
				IntelPrinter.Print(asmCode);
			}
			else {
				AttPrinter.Print(asmCode);
			}
			
			// return to original output stream
			System.setOut(orig_is);
			
			
		} catch (IOException e) {
			e.printStackTrace();
		} catch (CompilationError e) {
			System.err.println(e.getMessage());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
